{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5b49755a",
   "metadata": {},
   "source": [
    "# 原则一：开闭原则（Open Close Principle）\n",
    "\n",
    "## 定义\n",
    "```python\n",
    "\n",
    "Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.\n",
    "```\n",
    "\n",
    "即：一个软件实体如类、模块和函数应该对扩展开放，对修改关闭。\n",
    "\n",
    "\n",
    "## 定义解读\n",
    "- 用抽象构建框架，用实现扩展细节\n",
    "- 不以改动原有类的方式实现新需求，而是应该以实现实现抽象出来的接口（或具体类继承抽象类）的方式来实现\n",
    "\n",
    "## 优点\n",
    "实践开闭原则的优点在于可以在不改动原有代码的前提下给程序扩展功能。增加程序的可拓展性，同时也降低了程序的维护成本。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d4b90108",
   "metadata": {},
   "source": [
    "## 代码讲解\n",
    "\n",
    "### 需求点\n",
    "\n",
    "设计一个在线课程类：\n",
    "\n",
    "由于教学资源有限，开始的时候只有类似于博客的，通过文字讲解的课程。\n",
    "但是随着教学资源的增多，后来增加了视频课程，音频课程以及直播课程。\n",
    "\n",
    "先来看一下不好的设计："
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43946620",
   "metadata": {},
   "source": [
    "### 不好的设计\n",
    "\n",
    "最开始的文字课程类:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "46aed636",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Course(object):\n",
    "    def __init__(self, \n",
    "                 course_title,  # 课程名称\n",
    "                 course_introductoon, # 课程介绍\n",
    "                 teache_name,  # 讲师姓名\n",
    "                 content, # 课程内容\n",
    "                ):\n",
    "        self.course_title = course_title\n",
    "        self.course_introductoon = course_introductoon\n",
    "        self.teache_name = teache_name\n",
    "        self.content = content"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a78e29a9",
   "metadata": {},
   "source": [
    "Course类声明了最初的在线课程所需要包含的数据：\n",
    "\n",
    "- 课程名称\n",
    "- 课程介绍\n",
    "- 讲师姓名\n",
    "- 文字内容\n",
    "\n",
    "接着按照上面所说的需求变更：增加了视频，音频，直播课程："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "02f67929",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Course(object):\n",
    "    def __init__(self, \n",
    "                 course_title,  # 课程名称\n",
    "                 course_introductoon, # 课程介绍\n",
    "                 teache_name,  # 讲师姓名\n",
    "                 content, # 课程内容\n",
    "                 video_url=None,\n",
    "                 audio_url=None,\n",
    "                 live_url=None,\n",
    "                ):\n",
    "        self.course_title = course_title\n",
    "        self.course_introductoon = course_introductoon\n",
    "        self.teache_name = teache_name\n",
    "        self.content = content\n",
    "        \n",
    "        self.video_url = video_url\n",
    "        self.audio_url = audio_url\n",
    "        self.live_url = live_url"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c333f1c",
   "metadata": {},
   "source": [
    "三种新增的课程都在原Course类中添加了对应的url。也就是每次添加一个新的类型的课程，都在原有Course类里面修改：新增这种课程需要的数据。\n",
    "\n",
    "这就导致：我们从Course类实例化的视频课程对象会包含并不属于自己的数据：audioUrl和liveUrl：这样就造成了冗余，视频课程对象并不是纯粹的视频课程对象，它包含了音频地址，直播地址等成员。\n",
    "\n",
    "很显然，这个设计不是一个好的设计，因为（对应上面两段叙述）：\n",
    "\n",
    "随着需求的增加，需要反复修改之前创建的类。\n",
    "给新增的类造成了不必要的冗余。\n",
    "之所以会造成上述两个缺陷，是因为该设计没有遵循对修改关闭，对扩展开放的开闭原则，而是反其道而行之：开放修改，而且不给扩展提供便利。\n",
    "\n",
    "难么怎么做可以遵循开闭原则呢？下面看一下遵循开闭原则的较好的设计：\n",
    "\n",
    "## 较好的设计\n",
    "\n",
    "首先在Course类中仅仅保留所有课程都含有的数据："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "ef472e40",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Course(object):\n",
    "    def __init__(self, \n",
    "                 course_title,  # 课程名称\n",
    "                 course_introductoon, # 课程介绍\n",
    "                 teache_name,  # 讲师姓名 \n",
    "                ):\n",
    "        self.course_title = course_title\n",
    "        self.course_introductoon = course_introductoon\n",
    "        self.teache_name = teache_name \n",
    "        \n",
    "\n",
    "# 文字课程类：        \n",
    "class TextCourse(Course):\n",
    "    def __init__(self, \n",
    "                 course_title,  # 课程名称\n",
    "                 course_introductoon, # 课程介绍\n",
    "                 teache_name,  # 讲师姓名\n",
    "                 content, # 课程内容\n",
    "                ):\n",
    "        super().__init__(course_title=course_title, \n",
    "                         course_introductoon=course_introductoon,\n",
    "                         teache_name=teache_name)\n",
    "        self.content = content\n",
    "        \n",
    "        \n",
    "# 视频课程类：       \n",
    "class VideoCourse(Course):\n",
    "    def __init__(self, \n",
    "                 course_title,  # 课程名称\n",
    "                 course_introductoon, # 课程介绍\n",
    "                 teache_name,  # 讲师姓名\n",
    "                 video_url, # 视频地址\n",
    "                ):\n",
    "        super().__init__(course_title=course_title, \n",
    "                         course_introductoon=course_introductoon,\n",
    "                         teache_name=teache_name)\n",
    "        self.video_url = video_url\n",
    "        \n",
    "# 视频课程类：       \n",
    "class AudioCourse(Course):\n",
    "    def __init__(self, \n",
    "                 course_title,  # 课程名称\n",
    "                 course_introductoon, # 课程介绍\n",
    "                 teache_name,  # 讲师姓名\n",
    "                 audio_url,  # 音频地址\n",
    "                ):\n",
    "        super().__init__(course_title=course_title, \n",
    "                         course_introductoon=course_introductoon,\n",
    "                         teache_name=teache_name)\n",
    "        self.audio_url = audio_url\n",
    "        \n",
    "# 直播课程类：       \n",
    "class LiveCourse(Course):\n",
    "    def __init__(self, \n",
    "                 course_title,  # 课程名称\n",
    "                 course_introductoon, # 课程介绍\n",
    "                 teache_name,  # 讲师姓名\n",
    "                 live_url,  # 直播地址\n",
    "                ):\n",
    "        super().__init__(course_title=course_title, \n",
    "                         course_introductoon=course_introductoon,\n",
    "                         teache_name=teache_name)\n",
    "        self.live_url = live_url"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5ba2e243",
   "metadata": {},
   "source": [
    "这样一来，上面的两个问题都得到了解决：\n",
    "\n",
    "随着课程类型的增加，不需要反复修改最初的父类（Course），只需要新建一个继承于它的子类并在子类中添加仅属于该子类的数据（或行为）即可。\n",
    "因为各种课程独有的数据（或行为）都被分散到了不同的课程子类里，所以每个子类的数据（或行为）没有任何冗余。\n",
    "\n",
    "我们可以看到，正是由于最初程序设计合理，所以对后面需求的增加才会处理得很好。\n",
    "\n",
    "下面来看一下这两个设计的UML 类图，可以更形象地看出两种设计上的区别：\n",
    "\n",
    "## UML 类图对比\n",
    "\n",
    "未实践开闭原则：\n",
    "\n",
    "![101](pic/101-101.png)\n",
    "\n",
    "实践了开闭原则：\n",
    "\n",
    "![101](pic/101-102.png)\n",
    "\n",
    "在实践了开闭原则的 UML 类图中，四个课程类继承了Course类并添加了自己独有的属性。（在 UML 类图中：实线空心三角箭头代表继承关系：由子类指向其父类）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e09a848",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "dc92d942",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "465904ba",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e4400c5",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
